﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Threading.Tasks;

namespace TowerDefense_MLP
{
    class GameController
    {
        #region ATTRIBUTES
        private Dictionary<int, Tower> towersOnMap;
        private int idTower;
        private List<Wave> wavesOfEnemies;
        private int actualWave;
        private Map actualMap;
        private Bitmap moveEnemiesMap;
        private int _lifes;
        private int _money;
        private int _mapId;
        private bool _waveFinished;
        #endregion

        #region METHODS

        #region Getters/Setters

        public Dictionary<int, Tower> TowersOnMap
        {
            get { return towersOnMap; }
            set { towersOnMap = value; }
        }
        public Bitmap MapImage
        {
            get { return this.actualMap.MapImage; }
        }
        public int Money
        {
            get { return this._money; }
            set { this._money = value; }
        }
        public int Lifes
        {
            get { return this._lifes; }
        }
        public Bitmap MoveEnemiesImage
        {
            get { return this.moveEnemiesMap; }
        }
        internal Map ActualMap
        {
            get { return actualMap; }
            set { actualMap = value; }
        }
        public int NumWaves
        {
            get { return this.wavesOfEnemies.Count; }
        }
        public int ActualWave
        {
            get { return this.actualWave + 1; }
        }
        public bool WaveFinished
        {
            get { return this._waveFinished; }
        }
        #endregion

        public GameController(int mapId)
        {
            this._mapId = mapId;
            this.towersOnMap = new Dictionary<int, Tower>();
            this.idTower = 1;
            this.actualMap = new Map(mapId);
            this.wavesOfEnemies = this.actualMap.GetWavesInfos();
            this.actualWave = 0;
            this._lifes = 50;
            this._money = 500;
            this._waveFinished = false;
            this.moveEnemiesMap = this.actualMap.MapImage;
        }

        public bool PositionIsOcuppied(int x, int y)
        {
            return this.ActualMap.PositionIsOccupied(x, y);
        }
        
        public void AddTower(Tower newTower, int x, int y)
        {
            newTower.Position = new Point(x, y);

            TowersOnMap.Add( this.idTower, newTower);
            this.ActualMap.DrawTower(newTower);
            this.ActualMap.AddTowerToWorldGrid(newTower.Position.X, newTower.Position.Y, this.idTower++);
        }

        public bool UpgradeTower(int towerID)
        {
            if (this._money >= this.towersOnMap[towerID].Value)
            {
                this.towersOnMap[towerID].Upgrade();
                drawAllTowers();

                return true;
            }

            return false;
        }

        public void SellTower(int towerID)
        {
            this.actualMap.RemoveTowerToWorldGrid(this.towersOnMap[towerID].Position.X, this.towersOnMap[towerID].Position.Y);
            this.towersOnMap.Remove(towerID);
            drawAllTowers();
        }
        
        public void Iterate()
        {
            // move os inimigos
            this.wavesOfEnemies[this.actualWave].MoveEnemies(this.actualMap.WavePath, ref this._lifes);

            if (this._lifes > 0) // não morreu, por inimigos chegando no final
            {
                TOWER_TYPE tower;
                int damage;
                int reward;
                List<Enemy> enemiesList;
                List<Tower> towersReady = this.towersOnMap.Values.Where(t => t.ReadyToAtack()).ToList();

                towersReady.ForEach(t =>
                {
                    enemiesList = this.wavesOfEnemies[this.actualWave].EnemiesList.Where(e => EnemyIsInRange(t, e)).ToList();

                    if (enemiesList.Count > 0)
                    {
                        damage = t.Atack(out tower);

                        if (this.wavesOfEnemies[this.actualWave].KillEnemy(enemiesList[0], damage, tower, out reward))
                            this._money += reward;
                    }
                });

                this.moveEnemiesMap = new Bitmap(this.actualMap.MapImage);

                using (Graphics g = Graphics.FromImage(this.moveEnemiesMap))
                {
                    this.wavesOfEnemies[this.actualWave].EnemiesList.Where(en => (en.PathIndex >=0)).ToList().ForEach(e =>
                    {  
                        g.DrawImage(e.Image, e.Position);

                        //desenha tiro
                        switch (e.HitBy)
                        {
                            case TOWER_TYPE.CANON:
                                g.FillEllipse(new SolidBrush(Color.Chocolate), e.Position.X + 5, e.Position.Y + 5, e.Image.Width - 5, e.Image.Height - 5);
                                break;

                            case TOWER_TYPE.FIRE:
                                g.FillEllipse(new SolidBrush(Color.Red), e.Position.X + 5, e.Position.Y + 5, e.Image.Width - 5, e.Image.Height - 5);
                                break;

                            case TOWER_TYPE.WATER:
                                g.FillEllipse(new SolidBrush(Color.Blue), e.Position.X + 5, e.Position.Y + 5, e.Image.Width - 5, e.Image.Height - 5);
                                break;
                        }
                    });
                }
            }

            if (this.wavesOfEnemies[this.actualWave].EnemiesList.Count == 0 || this._lifes == 0)
            {
                if (this._lifes > 0 )
                    this.actualWave++;

                this._waveFinished = true;
            }
        }

        public int ElementAtPosition(int x, int y)
        {
            return this.ActualMap.ElementAt(x, y);
        }

        public void NextWave()
        {
            this._waveFinished = false;
        }

        public bool GameOver(out Consts gameState)
        {
            if (this.actualWave == this.wavesOfEnemies.Count || this._lifes == 0)
            {
                if (this._lifes > 0)
                    gameState = Consts.VICTORY;
                else
                    gameState = Consts.DEFEATED;

                return true;
            }

            gameState = Consts.DEFEATED;
            return false;
        }

        private void drawAllTowers()
        {
            this.ActualMap.updateMap();
            this.towersOnMap.Values.ToList().ForEach(t => this.actualMap.DrawTower(t));
        }

        // expressão Lambda para calcular a distância de dois pontos    
        Func<Point, Point, double> dist = (p1, p2) => Math.Sqrt(Math.Pow(p2.X - p1.X, 2) + Math.Pow(p2.Y - p1.Y, 2));
       
        private bool EnemyIsInRange(Tower t, Enemy e)
        {
            if (e.Position.X != -1 && e.Position.Y != -1)
            {
                Point tCenter = new Point(t.Position.X + t.TowerImage.Width / 2, t.Position.Y + t.TowerImage.Height / 2);
                Point eP = new Point(e.Position.X + e.Image.Width/2, e.Position.Y + e.Image.Height/2);

                if ( dist(tCenter, eP) <= t.Range / 2)
                    return true;
            }
            
            return false;
        }
        #endregion
    }
}
